﻿using System;
using System.Linq.Expressions;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Common;

namespace Nvigorate.Test
{
    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>
    [TestClass]
    public class UnitTest1
    {
        public UnitTest1()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestMethod]
        public void DynamicLINQ_Test1()
        {
            Type targetType = typeof (Widget);
            NewExpression newExpression = Expression.New(targetType);
            List<MemberBinding> bindings = new List<MemberBinding>();

            Dictionary<string, object> values = new Dictionary<string, object>() {{"Name", "Alex"}, {"Cost", 10.5m}};
            Dictionary<string, ParameterExpression> parameters = new Dictionary<string, ParameterExpression>();
            
            int pIndex = 0;
            foreach (var assignment in values.Keys)
            {
                Type propertyType = Reflector.GetMemberType(targetType, assignment);
                ParameterExpression parameter = Expression.Parameter(propertyType, "x" + (++pIndex).ToString());
                parameters.Add(assignment, parameter);
                bindings.Add(Reflector.GetPropertyBindExpression(targetType, assignment,
                                                                 parameter));
            }

            MemberInitExpression initExpression = Expression.MemberInit(newExpression, bindings);

            string expr = initExpression.ToString();
            Assert.IsFalse(expr == "");

            List<Type> argTypes = new List<Type>(parameters.Select(kvp => kvp.Value.Type));
            argTypes.Add(targetType);

            Type funcType = Expression.GetFuncType(argTypes.ToArray());
            LambdaExpression lambdaExpression = Expression.Lambda(funcType,
                                                                    initExpression, 
                                                                    parameters.Values.ToArray());
            Delegate lambda = lambdaExpression.Compile();
            Widget w = (Widget) lambda.DynamicInvoke("Alex", 10.5m);
            Assert.IsNotNull(w);
        }

        [TestMethod]
        public void DynamicLINQ_Test2()
        {
            Type targetType = typeof(Widget);
            Type propertyType = Reflector.GetMemberType(targetType, "Cost");

            ParameterExpression valueParam = Expression.Parameter(propertyType, "v");
            ParameterExpression instanceParam = Expression.Parameter(targetType, "i");

            MethodCallExpression methodExpression = Reflector.GetPropertySetExpression(targetType, "Cost", instanceParam, valueParam);
            
            string expr = methodExpression.ToString();
            Assert.IsFalse(expr == "");

            Type actionType = Expression.GetActionType(targetType, propertyType);
            LambdaExpression lambdaExpression = Expression.Lambda(actionType,
                                                                    methodExpression,
                                                                    instanceParam, valueParam);
            Action lambda = (Action) lambdaExpression.Compile();
            
            Widget w = new Widget() {Name = "Alex", Cost=10.5m};

            int runs = 0;
            for (decimal i = 0; i < 1000000; i+=0.1m)
            {
                lambda.DynamicInvoke(w, i);
                runs++;
            }
            Assert.AreEqual(10000000, runs);
        }

        public Action<I, P> GetWriteAction<I, P>(string propertyName)
        {
            Type targetType = typeof (I);
            Type propertyType = typeof (P);

            ParameterExpression valueParam = Expression.Parameter(propertyType, "v");
            ParameterExpression instanceParam = Expression.Parameter(targetType, "i");

            MethodCallExpression methodExpression = Reflector.GetPropertySetExpression(targetType, propertyName, instanceParam, valueParam);

            return null;
        }

        public Func<I, P, P> GetReadFunc<I, P>(string propertyName)
        {
            Type targetType = typeof(I);
            Type propertyType = typeof(P);

            ParameterExpression instanceParam = Expression.Parameter(targetType, "i");

            MethodCallExpression methodExpression = Reflector.GetPropertyGetExpression(targetType, propertyName, instanceParam);


            return null;
        }


        public class Widget
        {
            private string _name = "";
            private decimal _cost = 0m;

            public string Name
            {
                get { return _name; }
                set { _name = value; }
            }

            public decimal Cost
            {
                get { return _cost; }
                set { _cost = value; }
            }
        }
    }
}
